﻿using BaseProject.Domain.Models;
using BaseProject.Domain.Repositories;
using BaseProject.Infrastructure.Data;
using Dapper;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;

namespace BaseProject.Repositories
{
    public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity
    {
        /// <summary>
        /// 设置Dialect
        /// </summary>
        protected void Configure()
        {
            Dapper.SimpleCRUD.SetDialect(SimpleCRUD.Dialect.MySQL);
        }

        /// <summary>
        /// 插入数据
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public virtual async Task<bool> Insert(T t)
        {
            return await ExecuteWithCondition<bool>(c => c.Insert(t) > 0, Database.MASTER_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 更新数据
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public virtual async Task<bool> Update(T t)
        {
            return await ExecuteWithCondition(c => c.Update(t) > 0, Database.MASTER_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 删除数据
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual async Task<bool> Delete(long id)
        {
            return await ExecuteWithCondition(c =>
            {
                var entity = GetById(id);
                var obj = entity as T;
                return c.Delete(obj) > 0;
            }, Database.MASTER_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 执行sql
        /// </summary>
        /// <param name="sql"></param>
        /// <param name="param"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <param name="commandType"></param>
        /// <returns></returns>
        public virtual async Task<int> Execute(string sql, object param)
        {
            return await ExecuteWithCondition(c => c.Execute(sql, param), Database.MASTER_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 根据id获取数据
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual async Task<T> GetById(long id)
        {
            return await ExecuteWithCondition(c => c.Get<T>(id), Database.SLAVE_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 获取数据列表
        /// </summary>
        /// <returns></returns>
        public virtual async Task<IEnumerable<T>> GetList()
        {
            return await ExecuteWithCondition(c => c.GetList<T>(), Database.SLAVE_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 根据条件获取数据列表
        /// </summary>
        /// <returns></returns>
        public virtual async Task<IEnumerable<T>> GetList(string conditions, object param)
        {
            return await ExecuteWithCondition(c => c.GetList<T>(conditions, param), Database.SLAVE_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 获取分页数据列表
        /// </summary>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <param name="conditions"></param>
        /// <param name="orderby"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<T>> GetPagedList(int pageNumber, int pageSize, string conditions, string orderby, object param)
        {
            return await ExecuteWithCondition(c => c.GetListPaged<T>(pageNumber, pageSize, conditions, orderby, param), Database.SLAVE_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 获取数据总数
        /// </summary>
        /// <param name="conditions"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual async Task<int> RecordCount(string conditions, object param)
        {
            return await ExecuteWithCondition(c => c.RecordCount<T>(conditions, param), Database.SLAVE_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 根据sql语句获取数据列表
        /// </summary>
        /// <returns></returns>
        public virtual async Task<IEnumerable<T>> Query(string sql, object param)
        {
            return await ExecuteWithCondition(c => c.Query<T>(sql, param), Database.SLAVE_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 根据sql语句获取一条数据
        /// </summary>
        /// <returns></returns>
        public virtual async Task<T> QueryFirst(string sql, object param)
        {
            return await ExecuteWithCondition(c => c.QueryFirstOrDefault<T>(sql, param), Database.SLAVE_LOCAL_CONNECTION);
        }

        /// <summary>
        /// 根据sql语句获取数据总数
        /// </summary>
        /// <returns></returns>
        public virtual async Task<int> ExecuteScalar(string sql, object param)
        {
            return await ExecuteWithCondition(c => c.ExecuteScalar<int>(sql, param), Database.SLAVE_LOCAL_CONNECTION);
        }

        protected async Task<TT> ExecuteWithCondition<TT>(Func<IDbConnection, TT> execute, string conn)
        {
            try
            {
                Configure();
                using (var connection = Database.Connection(conn))
                {
                    await connection.OpenAsync();
                    return execute(connection);
                }
            }
            catch (TimeoutException ex)
            {
                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL timeout", ex);
            }
            catch (Exception ex)
            {
                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL exception (not a timeout)", ex);
            }
        }

        protected async Task<TT> ExecuteWithConditionAsync<TT>(Func<IDbConnection, Task<TT>> execute, string conn)
        {
            try
            {
                Configure();
                using (var connection = Database.Connection(conn))
                {
                    await connection.OpenAsync();
                    return await execute(connection);
                }
            }
            catch (TimeoutException ex)
            {
                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL timeout", ex);
            }
            catch (Exception ex)
            {
                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL exception (not a timeout)", ex);
            }
        }

        protected void ExecuteWithTransaction<TT>(Action<IDbConnection, IDbTransaction> execute, string conn)
        {
            try
            {
                Configure();
                using (var connection = Database.Connection(conn))
                {
                    connection.Open();
                    IDbTransaction transaction = connection.BeginTransaction();
                    try
                    {
                        execute(connection, transaction);
                    }
                    catch
                    {
                        transaction.Rollback();
                        throw;
                    }
                    finally
                    {
                        transaction.Commit();
                    }
                }
            }
            catch (TimeoutException ex)
            {
                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL timeout", ex);
            }
            catch (Exception ex)
            {
                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL exception (not a timeout)", ex);
            }
        }

        protected int ExecuteWithTransaction(IDbConnection conn, string sql, object param)
        {
            Configure();
            using (IDbTransaction transaction = conn.BeginTransaction())
            {
                int rowCount = 0;
                try
                {
                    rowCount = conn.Execute(sql, param, transaction);
                    transaction.Commit();
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
                return rowCount;
            }
        }

        protected async Task<int> ExecuteWithTransactionAsync(IDbConnection conn, string sql, object param)
        {
            Configure();
            using (IDbTransaction transaction = conn.BeginTransaction())
            {
                int rowCount = 0;
                try
                {
                    rowCount = await conn.ExecuteAsync(sql, param, transaction);
                    transaction.Commit();
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
                return rowCount;
            }
        }

        protected TT ExecuteWithConditionSync<TT>(Func<IDbConnection, TT> execute, string conn)
        {
            try
            {
                Configure();
                using (var connection = Database.Connection(conn))
                {
                    connection.Open();
                    return execute(connection);
                }
            }
            catch (TimeoutException ex)
            {
                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL timeout", ex);
            }
            catch (Exception ex)
            {
                throw new Exception($"{GetType().FullName}.WithConnection() experienced a SQL exception (not a timeout)", ex);
            }
        }
    }
}